package com.fzebu.fastboot.db.intl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.serializer.PropertyFilter;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import weaver.conn.RecordSetTrans;

import java.lang.reflect.Field;
import java.util.*;

/**
 * @author Jack
 * @version 1.0.0
 * @create 2020/2/16 19:25
 */
@Slf4j(topic = "debug")
public abstract class AbstractSQLBuilder {

    private final Map<String, Object> paramMap = new HashMap<>();

    private String sql;

    private boolean buildSql = true;

    @Getter
    private String tableName;

    @Setter
    protected RecordSetTrans trans;

    // 模版对外提供的方法
    public final boolean executeUpdate() {
        String sqlType = this.getSqlType();
        log.debug("Start Execute {} SQL", sqlType);
        // 1. 构建参数
        ArrayList<Object> params = this.buildParams();
        log.debug("DbUtil {} Parameters => \n{}", sqlType, params);
        // 2. 构建sql
        String sql = this.getSql();
        log.debug("DbUtil {} SQL => \n{}", sqlType, sql);
        boolean execResult;
        try {
            // 3. 执行sql语句
            execResult = this.execute(sql, params);
            log.debug("DbUtil {} SQL Result => {}", sqlType, execResult);
        } catch (Exception e) {
            log.error("DbUtil {} SQL ERROR => {}", sqlType, e.getMessage());
            throw new RuntimeException(e);
        }
        // 4. 执行成功的钩子
        this.processResult(execResult);
        this.buildSql = false;
        return execResult;
    }

    private boolean execute(String sql, ArrayList<Object> params) throws Exception {
        if (this.trans == null) {
            this.trans = new RecordSetTrans();
        }
        return this.trans.executeUpdate(sql, params);
    }

    public final String getSql() {
        if (!this.buildSql) {
            return this.sql;
        }
        if (StringUtils.isEmpty(this.tableName)) {
            throw new IllegalArgumentException("table name is null");
        }
        if (this.paramMap.size() == 0) {
            throw new IllegalArgumentException("params size is null");
        }
        this.sql = this.buildSql(this.tableName, this.paramMap);
        return this.sql;
    }

    /**
     * 刷新 SQL与 Parameter
     */
    public void refresh() {
        this.paramMap.clear();
        this.sql = null;
        this.buildSql = true;
    }

    protected final void setParam(String key, Object val) {
        key = StringUtils.upperCase(key);
        if (!this.paramMap.containsKey(key)) {
            this.buildSql = true;
            log.trace("next need to reset the SQL");
        }
        this.paramMap.put(key, val);
    }

    protected final void setTableName(String tableName) {
        this.tableName = StringUtils.upperCase(tableName);
    }

    public final void convert2Params(final Object obj) throws JSONException {
        // 初始化 paramMap
        this.paramMap.clear();
        Field[] fields = obj.getClass().getDeclaredFields();
        Set<String> set = new HashSet<>();
        for (Field field : fields) {
            set.add(field.getName());
        }
        String json = JSON.toJSONString(obj, (PropertyFilter) (object, name, value) -> set.contains(name));
        JSON.parseObject(json).forEach(this::setParam);
    }

    /**
     * 构建参数之前的预处理
     * @param paramMap  参数Map
     */
    protected void beforeBuildParams(Map<String, Object> paramMap) {}

    /**
     * 构建参数
     * @return  参数列表
     */
    protected final ArrayList<Object> buildParams() {
        // 执行构建参数前的钩子
        this.beforeBuildParams(this.paramMap);
        StringBuilder sb = new StringBuilder();
        this.paramMap.forEach((k, v) -> sb.append(k).append("——").append(v).append('\n'));
        log.debug("Parameter Map: \n{}", sb);
        // 执行构建参数后的钩子
        return this.afterBuildParams(new ArrayList<>(this.paramMap.values()));
    }

    /**
     * 构建参数之后的处理
     * @param params    SQL参数
     */
    protected ArrayList<Object> afterBuildParams(ArrayList<Object> params) { return params; }

    /**
     * 获取SQL类型
     * 模版函数，由子类负责实现
     * @return  任意字符串
     */
    public abstract String getSqlType();

    /**
     * 构建SQL
     * 模版函数，由子类负责实现
     * @return  SQL语句
     */
    protected abstract String buildSql(String table, Map<String, Object> paramMap);

    /**
     * SQL执行后的钩子函数
     * @param execResult    SQL是否执行成功
     */
    public void processResult(boolean execResult) {}
}
